export const SPEED_ARG = 0.0058;

export const DEFAULT_TRACK_SIZE = 12;

export const defaultDanmakuData = {
  msg: "",
  fontSize: 24,
  fontColor: "#ffffff",
  fontMode: "roll",
  fontArea: "full",
  fontAreaPercent: 0.25,
  rollTime: 0,
  rolledDistance: 0,
  top: 0,
};

import { getTranslateX } from "@/utils/dom";

export default class Danmaku {
  constructor(options) {
    // 弹幕容器（HTML 元素）
    this._container = options.container;
    // 容器宽度
    this._totalWidth = this._container.offsetWidth;
    // 容器高度
    this._totalHeight = this._container.offsetHeight;
    // 轨道高度 默认12px
    this._trackSize = 12;
    // 队列轮询间隔
    this._renderInterval = parseInt(options.renderInterval) || 150;
    // 队列轮询 setTimeout 计时器
    this._renderTimer = null;
    // 数据队列
    this._queue = [];
    // 轨道数据
    this._tracks = null;
    // 弹幕自编 id 累加器
    this._autoId = 0;

    // 初始化轨道数据结构
    this._resetTracks();
  }
  // 初始化轨道数据
  _resetTracks() {
    // 初始化轨道数据结构
    // 轨道数量
    const count = Math.floor(this._totalHeight / this._trackSize);
    // 轨道数据 初始化数据 这里是二维数组 每个轨道是一个数组，第二个纬 每个数组元素是弹幕的数据
    // [] 代表一个轨道，每个轨道有多个弹幕数据
    // 弹幕数据格式 { msg: "", fontSize: 24, fontColor: "#ffffff", fontMode: "roll", fontArea: "full", fontAreaPercent: 0.25, rollTime: 0, rolledDistance: 0, top: 0]
    this._tracks = new Array(count);
    for (let i = 0; i < count; i++) {
      this._tracks[i] = [];
    }
  }

  // 数据解析与复制
  _parseData(data) {
    return Object.assign(
      {
        autoId: ++this._autoId,
      },
      defaultDanmakuData,
      data
    );
  }

  // 添加弹幕数据到队列
  add(data) {
    // 弹幕数据解析与复制
    this._queue.push(this._parseData(data));
    // 如果队列轮询已经停止，则启动
    if (!this._renderTimer) {
      this._render();
    }
  }

  // 把弹幕数据放置到合适的轨道
  _addToTrack(data) {
    // 单条轨道
    let track;
    // 轨道的最后一项弹幕数据
    let lastItem;
    // 弹幕已经走的路程
    let distance;
    // 弹幕数据最终坐落的轨道索引
    // 有些弹幕会占多条轨道，所以 y 是个数组
    let y = [];

    for (let i = 0; i < this._tracks.length; i++) {
      // 遍历所有轨道，找到合适的轨道
      // 轨道被占用时，需要计算是否会重叠
      // 轨道未被占用时，直接使用即可
      track = this._tracks[i];

      if (track.length) {
        // 轨道被占用，要计算是否会重叠
        // 只需要跟轨道最后一条弹幕比较即可
        lastItem = track[track.length - 1];

        // 获取已滚动距离（即当前的 translateX）
        distance = -getTranslateX(lastItem.node);

        // 通过速度差，计算最后一条弹幕全部消失前，是否会与新增弹幕重叠
        // 如果不会重叠，则可以使用当前轨道
        if (
          distance > lastItem.width &&
          (data.rollSpeed <= lastItem.rollSpeed ||
            (distance - lastItem.width) /
              (data.rollSpeed - lastItem.rollSpeed) >
              (this._totalWidth + lastItem.width - distance) /
                lastItem.rollSpeed)
        ) {
          y.push(i);
        } else {
          y = [];
        }
      } else {
        // 轨道未被占用
        y.push(i);
      }

      // 有足够的轨道可以用时，就可以新增弹幕了，否则等下一次轮询
      if (y.length >= data.useTracks) {
        data.y = y;
        y.forEach((i) => {
          this._tracks[i].push(data);
        });
        break;
      }
    }
  }

  // （弹幕飘到尽头后）从轨道中移除对应数据
  _removeFromTrack(y, id) {
    y.forEach((i) => {
      const track = this._tracks[i];
      for (let j = 0; j < track.length; j++) {
        if (track[j].autoId === id) {
          track.splice(j, 1);
          break;
        }
      }
    });
  }

  // 通过 y 和 id 获取弹幕数据
  _findData(y, id) {
    const track = this._tracks[y];
    for (let j = 0; j < track.length; j++) {
      if (track[j].autoId === id) {
        return track[j];
      }
    }
  }

  // 轮询渲染
  _render() {
    try {
      this._renderToDOM();
    } finally {
      console.log(this._queue);
      this._renderEnd();
    }
  }

  // 渲染为 DOM 节点
  _renderToDOM() {
    const data = this._queue[0];
    let node = data.node;

    if (!node) {
      // 弹幕节点基本样式
      data.node = node = document.createElement("div");
      node.innerText = data.msg;
      node.style.position = "absolute";
      node.style.left = "100%";
      node.style.whiteSpace = "nowrap";
      node.style.color = data.fontColor;
      node.style.fontSize = data.fontSize + "px";
      node.style.willChange = "transform";
      this._container.appendChild(node);
      // 弹幕占用的轨道数  向下取整
      data.useTracks = Math.ceil(node.offsetHeight / this._trackSize);
      // 占用的轨道数多于轨道总数，则忽略此数据
      if (data.useTracks > this._tracks.length) {
        this._queue.shift();
        this._container.removeChild(node);
        return;
      }
      // 1个弹幕宽度
      data.width = node.offsetWidth;
      // 总位移 （弹幕宽度+显示区域宽度）
      data.totalDistance = data.width + this._totalWidth;
      // 位移时间（如果数据里面没有指定，就按照默认方式计算）
      data.rollTime =
        data.rollTime ||
        Math.floor(
          data.totalDistance * SPEED_ARG * (Math.random() * 0.3 + 0.7)
        );
      // 位移速度
      data.rollSpeed = data.totalDistance / data.rollTime;
    }

    this._addToTrack(data);

    if (data.y) {
      this._queue.shift();

      node.setAttribute("data-id", data.autoId);
      node.setAttribute("data-y", data.y[0]);
      node.style.top = data.y[0] * this._trackSize + "px";
      node.style.transition = `transform ${data.rollTime}s linear`;
      node.style.transform = `translateX(-${data.totalDistance}px)`;
      node.addEventListener(
        "transitionend",
        () => {
          this._removeFromTrack(data.y, data.autoId);
          this._container.removeChild(node);
        },
        false
      );
    }
  }

  // 轮询结束后，根据队列长度继续执行或停止执行
  _renderEnd() {
    if (this._queue.length > 0) {
      this._renderTimer = setTimeout(() => {
        this._render();
      }, this._renderInterval);
    } else {
      // 如果已经没有数据，就不再轮询了，等有数据时（add 方法中）再开启轮询
      this._renderTimer = null;
    }
  }
}
